package com.googlecode.mindlistening.tray;

import com.googlecode.mindlistening.PropertiesPropertiesStorage;
import com.googlecode.mindlistening.PropertiesStorage;
import com.googlecode.mindlistening.audio.ContinousAudioConvereter;
import com.googlecode.mindlistening.audio.FFMpegSplitter;
import com.googlecode.mindlistening.audio.RecordingFinishedHelper;
import com.googlecode.mindlistening.audio.RecordingFinishedManager;
import com.googlecode.mindlistening.audio.SimpleAudioRecorder;
import com.googlecode.mindlistening.audio.SplitAudioRecorder;
import com.googlecode.mindlistening.bookmarkHelper.BookmarkDropTargetWindow;
import com.googlecode.mindlistening.diarization.Diarizer;
import com.googlecode.mindlistening.webui.WebUI;
import com.googlecode.mindlistening.events.ExitApplicationMessage;
import com.googlecode.mindlistening.events.ScreenGrabStartEvent;
import com.googlecode.mindlistening.graph.PrefuseGraph;
import com.googlecode.mindlistening.module.liferecorder.FFMpegRecordingTasks;
import com.googlecode.mindlistening.module.liferecorder.LifeRecorderManager;
import com.googlecode.mindlistening.module.liferecorder.SansaClipLiferecorder;
import com.googlecode.mindlistening.module.liferecorder.UploadDirDialog;
import com.googlecode.mindlistening.module.liferecorder.Uploader;
import com.googlecode.mindlistening.screengrab.ScreenGrabber;
import com.googlecode.mindlistening.util.CouchLocator;
import com.googlecode.mindlistening.util.FFMpegLocator;
import com.googlecode.mindlistening.webui.AudioInternalJsonHandler;
import com.googlecode.mindlistening.webui.InternalJsonHandler;
import java.awt.Desktop;
import java.awt.MenuItem;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Member;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import org.apache.commons.lang.StringUtils;
import org.bushe.swing.event.EventBus;
import org.bushe.swing.event.EventServiceLocator;
import org.bushe.swing.event.EventSubscriber;
import org.bushe.swing.event.ThreadSafeEventService;
import org.ektorp.CouchDbConnector;
import org.ektorp.CouchDbInstance;
import org.ektorp.http.HttpClient;
import org.ektorp.http.StdHttpClient;
import org.ektorp.impl.StdCouchDbConnector;
import org.ektorp.impl.StdCouchDbInstance;

/**
 * Hello world!
 *
 */
public class App  {

    private PropertiesStorage p_storage;
    private File homeDir;
    private File recordingInProgressDir;
    private File recordingCompleteDir;
    private CouchDbConnector wikiConnector;
    private CouchDbInstance dbInstance;
    private HttpClient couchHttpClient;
    private Diarizer diarizer;
    RecordingFinishedManager manager;
    private BookmarkDropTargetWindow bdtw;
    private List<String> spaces;

    private Messages messages;
    WebUI ui;

    private String couchHost;
    private int couchPort = 0;
    private String couchDbName;
    private String wikidName;
    private ScreenGrabber screenGrabber;
    private JmDNS jmdns;

    public App() {
        System.setProperty(EventServiceLocator.SERVICE_NAME_EVENT_BUS, ThreadSafeEventService.class.getName());
        messages = loadMessages();
        try {
                    // Set cross-platform Java L&F (also called "Metal")
                UIManager.setLookAndFeel(
                    UIManager.getSystemLookAndFeelClassName());
            }
            catch (Exception e) {
               // handle exception
            }
    }

    protected void run() {
        initWorkingDirectory();
        loadPropertyStorage();
        findFFMpeg();
        startWebUI();
        List menuItems = createMenu();
        createTray(messages, menuItems);
        connectToCouch();
        startCouch();
        findSpaces();
        startLifeRecorderManager();
        initRecordingComponents();
        clustering();
        try {
            showUrl(new URL("http://" + couchHost + ":" + couchPort + "/" + wikidName));
        } catch (MalformedURLException ex) {
            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
        }
    }


    protected Messages loadMessages() {
        ResourceBundle bundle = ResourceBundle.getBundle("messages");
        return new Messages(bundle);
    }

    protected void createTray(final Messages messages, final List menuItems) {
        // Create the Tray
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Tray tray = new Tray("/eckoit.png", messages, menuItems);
            }
        });
    }

    protected List createMenu() {
        List menuItems = new ArrayList();
        menuItems.add(createSiteMenuItem());
        menuItems.add(Tray.MENU_SEPERATOR);
        menuItems.add(createBookmarkMenuItem());
        menuItems.add(createGraphMenuItem());
        menuItems.add(createUploadMenuItem());
        menuItems.add(Tray.MENU_SEPERATOR);
        menuItems.add(createExitMenuItem());
        
        return menuItems;
    }

    protected MenuItem createBookmarkMenuItem() {
        MenuItem item = new MenuItem(messages.getLocalMessage(Messages.BOOKMARK_TOOL_MENU_ITEM));
        item.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        if (bdtw == null) {
                            bdtw = new BookmarkDropTargetWindow(600, 0, spaces, dbInstance);
                        }
                        if (bdtw.isVisible()) {
                            bdtw.setVisible(false);
                        } else {
                            bdtw.setVisible(true);
                        }
                        
                    }
                });
            }
        });
        return item;
    }

    protected MenuItem createGraphMenuItem() {
        MenuItem item = new MenuItem(messages.getLocalMessage(Messages.GRAPH_TOOL_MENU_ITEM));
        item.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        // hover a window?
                        final PrefuseGraph g = new PrefuseGraph();
                        g.showGraph(couchHttpClient, couchDbName);
                    }
                });
            }
        });
        return item;
    }

    protected MenuItem createUploadMenuItem() {
        MenuItem item = new MenuItem("Upload Audio");
        item.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        UploadDirDialog dialog = new UploadDirDialog(null, true);
                        dialog.setWikiConnector(wikiConnector);
                        dialog.setLocationRelativeTo(null);
                        dialog.setVisible(true);
                    }
                });
            }
        });
        return item;
    }


    protected MenuItem createSiteMenuItem() {
        MenuItem item = new MenuItem(messages.getLocalMessage(Messages.NEW_MEETING_ITEM));
        item.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        try {
                            showUrl(new URL("http://" + couchHost + ":" + couchPort + "/" + wikidName ));
                        } catch (MalformedURLException ex) {
                            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                });
            }
        });
        return item;
    }

    protected MenuItem createExitMenuItem() {
        MenuItem item = new MenuItem(messages.getLocalMessage(Messages.EXIT_MENU_ITEM));
        item.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                EventBus.publish(new ExitApplicationMessage() );
                // Exit the app later
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        System.exit(0);

                    }
                });
            }
        });
        return item;
    }


    protected void startWebUI() {
        Map<String,InternalJsonHandler> internalHandlers = new HashMap<String, InternalJsonHandler>();
        AudioInternalJsonHandler aijh = new AudioInternalJsonHandler(recordingInProgressDir, recordingCompleteDir, SplitAudioRecorder.getSingletonObject());
        internalHandlers.put("/json/record", aijh);
        internalHandlers.put("/json/record/start", aijh);
        internalHandlers.put("/json/record/stop", aijh);
        internalHandlers.put("/json/record/status", aijh);


        // start the web server component
        ui = new WebUI(internalHandlers);
        try {
            ui.startWebUI();
        } catch (Exception ex) {
            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
        }

        // start the screenGrabber
        screenGrabber = new ScreenGrabber();

    }


    protected void startCouch() {
        CouchLocator locator = new CouchLocator(p_storage);
        CouchDBProcessManager couchManager = new CouchDBProcessManager(locator,wikiConnector, dbInstance, couchHttpClient);
        couchManager.ensureCouchDB();
    }



    public static void main(String[] args) {
        new App().run();
    }

    private void startLifeRecorderManager() {
        List<String> roots = new ArrayList<String>();
        String recorderRoot = p_storage.loadProperty("lifeRecorderDirs");
        if (StringUtils.isNotEmpty(recorderRoot)) {
            roots = Arrays.asList(recorderRoot.split(","));
        }

        SansaClipLiferecorder scl = new SansaClipLiferecorder();
        // fix this. this prop may be set later
        FFMpegSplitter splitter = new FFMpegSplitter(p_storage.loadProperty("ffmpegcmd"));
        FFMpegRecordingTasks recordingTasks = new FFMpegRecordingTasks(splitter);
        scl.setRecordingTasks(recordingTasks);
        scl.setSecondsAfterMark(20000);
        scl.setSecondsBeforeMark(10000);
        scl.setSplitMillisecondTollerance(2000);

        Uploader uploader = new Uploader(wikiConnector);

        LifeRecorderManager lrm = new LifeRecorderManager(scl, recordingInProgressDir, uploader, wikiConnector, couchHttpClient, messages, roots);
        lrm.start();
    }

    protected void showUrl(URL dest) {
        Desktop desktop = null;
        // Before more Desktop API is used, first check
        // whether the API is supported by this particular
        // virtual machine (VM) on this particular host.
        if (Desktop.isDesktopSupported()) {
            desktop = Desktop.getDesktop();
            try {
                desktop.browse(dest.toURI());
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(null,
                "Exception: " + ex.getMessage());
            }
        }
    }

    protected void findFFMpeg() {
        // if not set, find ffmpeg
        String ffmpegCmd = p_storage.loadProperty("ffmpegcmd");
        if (StringUtils.isEmpty(ffmpegCmd)) {
            FFMpegLocator locator = new FFMpegLocator(p_storage);
            new Thread(locator).start();
        }
    }



    protected void initWorkingDirectory() {
       String userHome = System.getProperty("user.home");
       homeDir = new File(userHome, ".mind-listening");
       if (!homeDir.exists()) {
           homeDir.mkdirs();
       }
       File recordingDir = new File(homeDir, "recordings");
       if (!recordingDir.exists()) {
           recordingDir.mkdirs();
       }

       recordingInProgressDir = new File(recordingDir, "in_progress");
       if (!recordingInProgressDir.exists()) {
           recordingInProgressDir.mkdirs();
       }

       recordingCompleteDir = new File(recordingDir, "complete");
       if (!recordingCompleteDir.exists()) {
           recordingCompleteDir.mkdirs();
       }


    }

    protected void loadPropertyStorage() {
        try {
            p_storage = new PropertiesPropertiesStorage(homeDir);
        } catch (IOException ex) {
            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    protected void initRecordingComponents() {
        String splitTime = p_storage.loadProperty("splitTime");
        if (StringUtils.isNotBlank(splitTime)) {
            try {
                SplitAudioRecorder.setSplitTime(Long.parseLong(splitTime));
            } catch (Exception e) {}
        }

        ContinousAudioConvereter cac = new ContinousAudioConvereter(p_storage, recordingInProgressDir, recordingCompleteDir);
        cac.start();

        RecordingFinishedHelper helper = new RecordingFinishedHelper(recordingInProgressDir, recordingCompleteDir);
        manager = new RecordingFinishedManager(dbInstance, helper);

        diarizer = new Diarizer(manager);
    }

    protected void connectToCouch() {
        // get the properties, if in the file
        couchHost = p_storage.loadProperty("couchdb_host");
        if (StringUtils.isEmpty(couchHost)) couchHost = "localhost";
        try {
            couchPort = Integer.parseInt(p_storage.loadProperty("couchdb_port"));
        } catch (Exception e) {}
        if (couchPort == 0) couchPort = 80;
        couchDbName = p_storage.loadProperty("couchdb_dbname");
        if (StringUtils.isEmpty(couchDbName)) couchDbName = "wikid";
        wikidName = "_" + couchDbName;
        //if (StringUtils.isEmpty(wikidName)) wikidName = "_wikid";


        couchHttpClient = new StdHttpClient.Builder()
                                    .host(couchHost)
                                    .port(couchPort)
                                    .build();

        dbInstance = new StdCouchDbInstance(couchHttpClient);
        this.wikiConnector = new StdCouchDbConnector(couchDbName, dbInstance);

    }

    protected void findSpaces() {
        spaces = new ArrayList<String>();
        List<String> dbs = dbInstance.getAllDatabases();
        for(String db: dbs) {
            if (!db.startsWith("_") && !db.endsWith("_")) {
                spaces.add(db);
            }
        }
    }

    protected void clustering() {
        try {
            jmdns = JmDNS.create();

            final HashMap<String, String> values = new HashMap<String, String>();
            values.put("DvNm", "Android-");
            values.put("RemV", "10000");
            values.put("DvTy", "iPod");
            values.put("RemN", "Remote");

            ServiceInfo pairservice = ServiceInfo.create("_http._tcp.eckoit.", "eckoit", couchPort,  0, 0, values);
            jmdns.registerService(pairservice);

            jmdns.addServiceListener("_http._tcp.eckoit.", new SampleListener());

            jmdns.printServices();

            EventBus.subscribeStrongly(ExitApplicationMessage.class, new EventSubscriber<ExitApplicationMessage>() {

                @Override
                public void onEvent(ExitApplicationMessage t) {
                    jmdns.unregisterAllServices();
                    try {
                        jmdns.close();
                    } catch (IOException ex) {
                        Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            });

            
        } catch (Exception ex) {
            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    static class SampleListener implements ServiceListener
    {
        @Override
        public void serviceAdded(ServiceEvent event)
        {
            System.out.println("Service added   : " + event.getName() + "." + event.getType() + " " + event.getDNS().getHostName());
        }

        @Override
        public void serviceRemoved(ServiceEvent event)
        {
            System.out.println("Service removed : " + event.getName() + "." + event.getType());
        }

        @Override
        public void serviceResolved(ServiceEvent event)
        {
            System.out.println("Service resolved: " + event.getInfo());
        }
    }

}
